package com.googlecode.raisinrefinery.grapevinescanner;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.TreeMap;

public class Main {

	private static class Description {
	    public Description(String format, String value) {
	        this.format = format;
	        this.value = value;
	    }
		String format;
		String value;
	}
	
	/**
	 * @param args
	 */
	public static void main(String[] args) throws Exception {
	    if (args.length != 2) {
	        System.err.println("Required args: listFuctions|checkBoolReturns|checkHresultReturns /path/to/file.c");
	        System.exit(1);
	    }
	    
	    if (args[0].equals("traceFunctions")) {
	    } else if (args[0].equals("checkBoolReturns")) {
	    } else if (args[0].equals("checkHresultReturns")) {
	    } else {
	        System.err.println("Unknown request '" + args[0] + "'");
	        System.exit(1);
	    }
	    
	    InputStream inputStream = null;
		BufferedReader reader = null;
		try {
		    inputStream = new FileInputStream(args[1]);
		    StringBuilder firstComment = new StringBuilder();
		    Map<String,String> defines = new TreeMap<String,String>();
		    ByteArrayOutputStream preprocessedFileStream = Preprocessor.preprocess(inputStream, firstComment, defines);
		    byte[] preprocessedFile = preprocessedFileStream.toByteArray();
		    
    		reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(preprocessedFile)));
    		String line;
    		StringBuilder buffer = new StringBuilder();
    		int curlyBraces = 0;
    		String functionDeclaration = "";
    		String functionBody = "";
    		while ((line = reader.readLine()) != null) {
    			for (int i = 0; i < line.length(); i++) {
    				char c = line.charAt(i);
    				if (curlyBraces == 0) {
    					if (c == ';' || c == '}') {
    						buffer.setLength(0);
    					} else if (c == '{') {
    						functionDeclaration = buffer.toString();
    						buffer.setLength(0);
    						++curlyBraces;
    					} else {
    						buffer.append(c);
    					}
    				} else if (curlyBraces >= 1) {
    					if (c == '}') {
    						--curlyBraces;
    						if (curlyBraces == 0) {
    							functionBody = buffer.toString();
    							buffer.setLength(0);
    		
    							if (args[0].equals("listFunctions")) {
    							    doFunction(functionDeclaration, functionBody);
    							} else if (args[0].equals("checkBoolReturns")) {
    							    doBools(functionDeclaration, functionBody);
    							} else if (args[0].equals("checkHresultReturns")) {
    							    doHesults(functionDeclaration, functionBody);
    							}
    						} else {
    							buffer.append(c);
    						}
    					} else if (c == '{') {
    						++curlyBraces;
    						buffer.append(c);
    					} else {
    						buffer.append(c);
    					}
    				}
    			}
    			buffer.append("\n");
    		}
		} finally {
		    safelyClose(reader);
		}
	}
	
    private static void doFunction(String decl, String body) throws IOException {
		String flatDecl = flattenLines(decl);
		
		int openBracket = flatDecl.lastIndexOf('(');
		int closeBracket = flatDecl.lastIndexOf(')');
		if (openBracket < 0 || closeBracket < 0) {
		    return;
		}
        int startOfFunctionName = openBracket - 1;
        for (; flatDecl.charAt(startOfFunctionName) == ' '; startOfFunctionName--) {
        }
        startOfFunctionName = flatDecl.lastIndexOf(' ', startOfFunctionName);
        for (; flatDecl.charAt(startOfFunctionName) != '_' &&
                !Character.isLetter(flatDecl.charAt(startOfFunctionName)); startOfFunctionName++) {
        }
	    String preamble = flatDecl.substring(0, startOfFunctionName);
		String functionName = flatDecl.substring(startOfFunctionName, openBracket);
		functionName = functionName.trim();
		System.out.println("FUNCTION NAME: " + functionName);
//			System.out.println("FUNCTION DECL:");
//			System.out.println(decl);
//			System.out.println("FUNCTION BODY:");
//			System.out.println(body);
		
//			System.out.println("FUNCTION ARGS:");
		String[] args = argsOf(flatDecl.substring(openBracket + 1, closeBracket));
		StringBuilder format = new StringBuilder();
		StringBuilder value = new StringBuilder();
		final boolean isCOMMethod = functionName.contains("_") && body.contains("This");
		String formatSeparator = "";
		String valueSeparator = "";
		for (int i = 0; i < args.length; i++) {
		    String arg = args[i];
//				System.out.println("  " + arg);
			ArrayList<String> argType = new ArrayList<String>();
			String argName = ""; 
			StringTokenizer spaceTokenizer = new StringTokenizer(arg, " ", false);
			while (spaceTokenizer.hasMoreTokens()) {
				StringTokenizer starTokenizer = new StringTokenizer(spaceTokenizer.nextToken(), "*", true);
				while (starTokenizer.hasMoreTokens()) {
					argName = starTokenizer.nextToken();
					argType.add(argName);
//						System.out.println("    " + argName);
				}
			}
			if (argType.size() > 0) {
				argType.remove(argType.size() - 1);
			}
			Description description;
			if (argType.size() == 0 && argName.equals("void")) {
				description = new Description("", "");
			} else {
				description = describeArg(argType, argName);
			}

			if (!isCOMMethod || i > 0) {
                format.append(formatSeparator);
                format.append(description.format);
                formatSeparator = ", ";
            } else {
                description.value = "This";
            }

			if (description.value.length() > 0) {
				value.append(valueSeparator);
				value.append(description.value);
			}
			valueSeparator = ", ";
		}
		if (isCOMMethod) {
		    System.out.println("    TRACE(\"%p->(" + format + ")\\n\", " + value + ");");
		} else {
		    System.out.println("    TRACE(\"(" + format + ")\\n\", " + value + ");");
		}
	}

    private static void doBools(String decl, String body) throws IOException {
        String flatDecl = flattenLines(decl);
        
        int openBracket = flatDecl.lastIndexOf('(');
        int closeBracket = flatDecl.lastIndexOf(')');
        if (openBracket < 0 || closeBracket < 0) {
            return;
        }
        int startOfFunctionName = openBracket - 1;
        for (; flatDecl.charAt(startOfFunctionName) == ' '; startOfFunctionName--) {
        }
        startOfFunctionName = flatDecl.lastIndexOf(' ', startOfFunctionName);
        for (; flatDecl.charAt(startOfFunctionName) != '_' &&
                !Character.isLetter(flatDecl.charAt(startOfFunctionName)); startOfFunctionName++) {
        }
        String preamble = flatDecl.substring(0, startOfFunctionName);
        String functionName = flatDecl.substring(startOfFunctionName, openBracket);
        functionName = functionName.trim();
        
        boolean returnsBoolean = flattenLines(preamble).indexOf("BOOL") >= 0;
        if (!returnsBoolean) {
            return;
        }
        
        String flatBody = flattenLines(body);
        for (int nextReturnPos = flatBody.indexOf("return "); nextReturnPos >= 0;
                nextReturnPos = flatBody.indexOf("return ", nextReturnPos+1)) {
            int semi = flatBody.indexOf(";", nextReturnPos);
            String expr = flatBody.substring(nextReturnPos + "return ".length(), semi).trim();
            boolean isNumber = false;
            try {
                Integer.parseInt(expr);
                isNumber = true;
            } catch (Exception ex) {
            }
            if (expr.equals("TRUE") || expr.equals("FALSE")) {
            } else if (expr.contains("==") || expr.contains(">") || expr.contains("<")) {
            } else if (isNumber) {
                System.out.println("Function " + functionName + " returns " + expr + " which isn't BOOL");
            } else {
                String[] lines = flatBody.split(";");
                boolean isBool = false;
                for (String line : lines) {
                    line = line.trim();
                    if (line.contains("BOOL") && line.contains(expr)) {
                        isBool = true;
                        break;
                    }
                }
                if (!isBool) {
                    System.out.println("Function " + functionName + " returns " + expr + " which might not be BOOL");
                }
            }
        }
    }

    private static void doHesults(String decl, String body) throws IOException {
        String flatDecl = flattenLines(decl);
        
        int openBracket = flatDecl.lastIndexOf('(');
        int closeBracket = flatDecl.lastIndexOf(')');
        if (openBracket < 0 || closeBracket < 0) {
            return;
        }
        int startOfFunctionName = openBracket - 1;
        for (; flatDecl.charAt(startOfFunctionName) == ' '; startOfFunctionName--) {
        }
        startOfFunctionName = flatDecl.lastIndexOf(' ', startOfFunctionName);
        for (; flatDecl.charAt(startOfFunctionName) != '_' &&
                !Character.isLetter(flatDecl.charAt(startOfFunctionName)); startOfFunctionName++) {
        }
        String preamble = flatDecl.substring(0, startOfFunctionName);
        String functionName = flatDecl.substring(startOfFunctionName, openBracket);
        functionName = functionName.trim();
        
        boolean returnsHresult = flattenLines(preamble).indexOf("HRESULT") >= 0;
        if (!returnsHresult) {
            return;
        }
        
        String flatBody = flattenLines(body);
        for (int nextReturnPos = flatBody.indexOf("return "); nextReturnPos >= 0;
                nextReturnPos = flatBody.indexOf("return ", nextReturnPos+1)) {
            int semi = flatBody.indexOf(";", nextReturnPos);
            String expr = flatBody.substring(nextReturnPos + "return ".length(), semi).trim();
            boolean isNumber = false;
            try {
                Integer.parseInt(expr);
                isNumber = true;
            } catch (Exception ex) {
            }
            if (expr.equals("S_OK") || expr.equals("S_FALSE") || expr.equals("CLASS_E_NOAGGREGATION") ||
                    expr.startsWith("E_")) {
            } else if (isNumber) {
                System.out.println("Function " + functionName + " returns " + expr + " which isn't a HRESULT constant");
            } else {
                String[] lines = flatBody.split(";");
                boolean isHresult = false;
                for (String line : lines) {
                    line = line.trim();
                    if (line.contains("HRESULT") && line.contains(expr)) {
                        isHresult = true;
                        break;
                    }
                }
                if (!isHresult) {
                    System.out.println("Function " + functionName + " returns " + expr + " which might not be a HRESULT");
                }
            }
        }
    }

	private static Description describeArg(ArrayList<String> argType, String name) {
		String first = argType.get(0);
		if (argType.size() == 1) {
			if (first.equals("REFIID") ||
					first.equals("REFGUID")) {
			    return new Description("%s", "debugstr_guid(" + name + ")"); 
			}
			if (first.equals("DWORD") ||
					first.equals("ULONG") ||
					first.equals("UINT")) {
				return new Description("%u", name);
			}
			if (first.equals("int") ||
			        first.equals("INT") ||
			        first.equals("LONG") ||
					first.equals("BOOL")) {
				return new Description("%d", name);
			}
			if (first.equals("LPCSTR")) {
			    return new Description("%s", "debugstr_a(" + name + ")");
			}
			if (first.equals("LPCWSTR")) {
			    return new Description("%s", "debugstr_w(" + name + ")");
			}
			if (first.equals("LPSTR") ||
			        first.equals("LPWSTR")) {
			    // can't be sure this isn't an output parameter...
			    return new Description("%p", name);
			}
			if (first.equals("LPDWORD")) {
			    return new Description("%p", name);
			}
			if (first.equals("LPARAM") || first.equals("LONG_PTR") || first.equals("DWORD_PTR") ||
			        first.equals("WPARAM") || first.equals("UINT_PTR") || first.equals("ULONG_PTR")) {
			    return new Description("%p", name);
			}
			if (first.equals("HWND") || first.equals("HICON") || first.equals("HANDLE")) {
			    return new Description("%p", name);
			}
			// educated guesses from here on:
			if (first.startsWith("LP")) {
			    return new Description("%p", name);
			}
            if (first.startsWith("H")) {
                return new Description("%p", name);
            }
		}
		
		boolean isPointer = false;
		for (String part : argType) {
			if (part.equals("*")) {
				isPointer = true;
			}
		}
		if (isPointer) {
			return new Description("%p", name);
		}
		
		return new Description("***UNKNOWN***", name);
	}
	
	private static String flattenLines(String lines) throws IOException {
		BufferedReader r = null;
		StringWriter w = null;
		try {
			r = new BufferedReader(new StringReader(lines));
			w = new StringWriter();
			String line;
			String sep = "";
			while ((line = r.readLine()) != null) {
				w.write(sep);
				sep = " ";
				w.write(line);
			}
			String flatDecl = w.toString();
			return flatDecl;
		} finally {
			safelyClose(r);
			safelyClose(w);
		}
	}
	
	private static String[] argsOf(String args) {
		String[] aa = args.split(",");
		String[] ret = new String[aa.length];
		for (int i = 0; i < aa.length; i++) {
			ret[i] = aa[i].trim();
		}
		return ret;
	}

	private static void safelyClose(Closeable c) {
		try {
			if (c != null) {
				c.close();
			}
		} catch (IOException ioex) {
		}
	}
}
